﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Threading;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using EliteServer;
using System.Text.RegularExpressions;

namespace Elite
{
    class Program
    {
        private static ConcurrentBag<Star> _galaxy = new ConcurrentBag<Star>();
        private static int _systemsCount = 0;
        private static Random _rndTable = new Random();
        private static Player _player;
        private static Server _server;
        private static DateTime _time;
        private static Stopwatch _timer;

        public static void Main(string[] args)
        {
            Console.BackgroundColor = ConsoleColor.White;
            Console.ForegroundColor = ConsoleColor.Black;
            
            Console.Title = "Elite - Programme principal";

            //GenerateGalaxy();

            // Démarrage du serveur
            DemarrerServeur();

            // Génération du système solaire
            GenererSystemeSolaire();            

            // démarrage du jeu
            w("*** Elite ***");
            w("1. Nouvelle Partie");

            if (File.Exists(@"..\Bin\manualSave.sav"))
                w("2. Reprendre Partie");

            w(">");

            var choice = Console.ReadLine();

            switch (choice)
            {
                case "1":
                    NewGame();
                    MainLoop();
                    break;
                case "2":
                    LoadGame();
                    MainLoop();
                    break;
                default:
                    break;
            }         
        }

        private static void NewGame()
        {
            _time = new DateTime(3256, 4, 24, 10, 0, 0, 0);
            
            _player = new Player { Account = 1000 };
            _player.ActualPlanet = FindStarAndPlanetFromPlanetName("Terre").Item2;
            _player.ActualPosition = FindStarAndPlanetFromPlanetName("Terre").Item1;
            _player.ActualState = State.SurUnePlanete;

            Console.Clear();
            w("Entez votre prénom suivit de votre nom (espace pour le séparateur :");
            _player.Name = Console.ReadLine();

            Console.Clear();
            w("Bienvenue {0}. Votre aventure commence au spatioport de Cap-Canaveral en Floride, sur Terre.", _player.Name);
            w("Appuyez sur une touche pour démarrer !");
            w(">");
            Console.ReadKey();
        }

        static void LoadGame()
        {
            using (var sR = new StreamReader(@"..\Bin\manualSave.sav"))
            {
                _player = new Player();
                var lines = Regex.Split(sR.ReadToEnd(), "\r\n");

                for (int i = 0; i < lines.Length; i++)
                {
                    switch (i)
                    {
                        case 0:
                            _player.Name = lines[i].Split('|')[1].Trim();
                            break;
                        case 1:
                            _player.Account = Convert.ToInt32(lines[i].Split('|')[1].Trim());
                            break;
                        case 2:
                            _player.ActualShip.Name = lines[i].Split('|')[1].Trim();
                            break;
                        case 3:
                            _time = Convert.ToDateTime(lines[i].Split('|')[1].Trim());
                            break;
                        case 4:
                            var planetName = lines[i].Split('|')[1].Trim();
                            if (planetName != "")
                                _player.ActualPlanet = FindStarAndPlanetFromPlanetName(lines[i].Split('|')[1].Trim()).Item2;
                            break;
                        case 5:
                            var systemName = lines[i].Split('|')[1].Trim();
                            if (systemName != "")
                                _player.ActualPosition = FindStarAndPlanetFromPlanetName(_player.ActualPlanet.Name).Item1;
                            break;
                        default:
                            break;
                    }
                }

                sR.Close();
            }
        }

        private static void MainLoop()
        {
            _timer = new Stopwatch();
            _timer.Start();

            // démarrage de la console d'affichage des infos
            Process.Start(@"..\Bin\EliteInfos.exe", "\"" + CurrentDateString() + "\r\n" + _player.Serialize() + "\"");
            
            var continuer = true;

            while (continuer)
            {
                switch (_player.ActualState)
                {
                    case State.SurUnePlanete:
                        Console.Clear();
                        w("Planète {0}", _player.ActualPlanet.Name);
                        w("--------------");
                        w("1. Décoller");
                        w("2. Faire du commerce");
                        w("3. Vérifier votre vaisseau");
                        w(">");
                        var rep = Console.ReadLine();
                        switch (rep)
                        {
                            case "1":
                                _player.ActualState = State.DansLespace;
                                break;
                            case "2":
                                _player.ActualState = State.DansLaZoneCommerciale;
                                break;
                            case "3":
                                _player.ActualState = State.DansLeHangar;
                                break;
                            case "/exit":
                                _server.Close();
                                continuer = false;
                                break;
                            case "/save":
                                SaveGame("manualSave");
                                break;
                            default:
                                break;
                        }
                        break;
                    case State.DansLaZoneCommerciale:
                        Console.Clear();
                        var location = string.Format("Planète {0} - Zone Commerciale", _player.ActualPlanet.Name);
                        _server.SendToEliteInfos(CurrentDateString() + "\r\n" + _player.Serialize());

                        w(location);
                        w("--------------");
                        w("Vous entrez dans la zone commerciale. La capitale galactique est aussi une plateforme comerciale gigantesque. Nombreux sont les produits et les représentants !!");
                        w("1. Consulter les produits");
                        w("2. Vendre des produits");
                        w(">");
                        rep = Console.ReadLine();
                        _player.ActualState = State.SurUnePlanete;
                        break;
                    case State.DansLeHangar:
                        Console.Clear();
                        w("Planète {0} - Hangar de votre vaisseau", _player.ActualPlanet.Name);
                        w("--------------");
                        w("Vous accédez à votre hangar pour vérifier les différents niveaux ...");
                        rep = Console.ReadLine();
                        _player.ActualState = State.SurUnePlanete;
                        break;
                    case State.DansLespace:
                        Console.Clear();

                        w("Vous allumez les moteurs de manoeuvre et vous dirigez vers l'orbite ...");
                        w(">");
                        Console.ReadKey();
                        _server.SendToEliteInfos(CurrentDateString() + "\r\n" + _player.Serialize());

                        Console.Clear();
                        w("En orbite autour de {0}", _player.ActualPlanet.Name);
                        w("--------------");
                        w("1. Choisir une destination");
                        w("2. Scanner les environs");
                        w("3. Atterir sur {0}", _player.ActualPlanet.Name);
                        w(">");
                        rep = Console.ReadLine();
                        switch (rep)
                        {
                            case "1":
                                _player.ActualState = State.DansLespace;
                                break;
                            case "2":
                                _player.ActualState = State.DansLespace;
                                break;
                            case "3":
                                Console.Clear();
                                w("Vous entrez dans l'atmosphère de {0} et rejoignez le spatioport le plus proche !", _player.ActualPlanet.Name);
                                w(">");
                                Console.ReadKey();
                                _player.ActualState = State.SurUnePlanete;
                                break;
                            case "/exit":
                                _server.Close();
                                continuer = false;
                                break;
                            default:
                                break;
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        private static void DemarrerServeur()
        {
            var tasks = new Task[] {
            Task.Factory.StartNew(() =>
                {
                    _server = new Server();
                    _server.StartServer();
                    _server.Loop();
                })
            };
        }

        private static void GenererSystemeSolaire()
        {
            var sun = new Star { Name = "Soleil", Class = "G", ID = "G-1", Coordinates = new double[3] { 0, 0, 0 } };
            var earth = new Planet { ID = "P-1", Name = "Terre", DistanceFromStar = 1, RelativeToEarthSize = 1, RelativeToEarthWeight = 1 };
            var moon = new Planet { ID = "P-2", Name = "Lune", DistanceFromStar = 0.9, RelativeToEarthSize = 0.3, RelativeToEarthWeight = 0.1 };
            var mars = new Planet { ID = "P-3", Name = "Mars", DistanceFromStar = 3.5, RelativeToEarthSize = 0.5, RelativeToEarthWeight = 0.3 };

            sun.Planets.Add(earth);
            sun.Planets.Add(moon);
            sun.Planets.Add(mars);

            _galaxy.Add(sun);
        }

        private static void GenerateGalaxy()
        {
            // génération d'une galaxie aléatoire            
            var galaxySize = 300000;
            var cores = Environment.ProcessorCount - 1;
            var modulo = 0;

            Math.DivRem(galaxySize, cores, out modulo);

            if (modulo != 0)
                galaxySize -= modulo;

            var stopwatch = new Stopwatch();
            stopwatch.Start();

            Parallel.For(0, cores, i =>
            {
                GenerateSystems(galaxySize / cores);
            });

            //for (int i = 0; i < galaxySize; i++)
            //{
            //    GenerateSystem();
            //}

            stopwatch.Stop();

            // reporting
            double nbPlanets = _galaxy.Sum(x => x.Planets.Count);
            double nbEmptySystems = _galaxy.Where(x => x.Planets.Count == 0).Count();
            double nbEarthSizePlanets = _galaxy.SelectMany(x => x.Planets).Where(x => x.RelativeToEarthSize > 0.8 && x.RelativeToEarthSize < 1.2).Count();
            double nbEarthWeightPlanets = _galaxy.SelectMany(x => x.Planets).Where(x => x.RelativeToEarthWeight > 0.8 && x.RelativeToEarthWeight < 1.2).Count();
            double nbEarthSizeAndWeightPlanets = _galaxy.SelectMany(x => x.Planets).
                Where(x => x.RelativeToEarthSize > 0.8 && x.RelativeToEarthSize < 1.2).
                Where(x => x.RelativeToEarthWeight > 0.8 && x.RelativeToEarthWeight < 1.2).Count();
            double nbEarthAtmospherePlanets = _galaxy.SelectMany(x => x.Planets).
                Where(x => x.Atmosphere.NRatio > 77 && x.Atmosphere.NRatio < 79).
                Where(x => x.Atmosphere.O2Ratio > 20 && x.Atmosphere.O2Ratio < 22).
                Count();
            double nbFullDataEarthLike = _galaxy.SelectMany(x => x.Planets).
                Where(x => x.RelativeToEarthSize > 0.8 && x.RelativeToEarthSize < 1.2).
                Where(x => x.RelativeToEarthWeight > 0.8 && x.RelativeToEarthWeight < 1.2).
                Where(x => x.Atmosphere.NRatio > 77 && x.Atmosphere.NRatio < 79).
                Where(x => x.Atmosphere.O2Ratio > 20 && x.Atmosphere.O2Ratio < 22).
                Count();

            using (var sW = new StreamWriter(@"C:\galaxy.txt"))
            {
                sW.WriteLine("Temps de génération : {0}s", stopwatch.Elapsed.TotalSeconds);
                sW.WriteLine("Nombre de planètes générées : {0}, soit {1} par système en moyenne", nbPlanets, (nbPlanets / _systemsCount).ToString("#.##"));
                sW.WriteLine("Nombre de systèmes sans planètes : {0}, soit {1}% des systèmes", nbEmptySystems, (nbEmptySystems / _systemsCount * 100).ToString("#.##"));
                sW.WriteLine("Nombre de planètes de la taille de la Terre à +/- 20% : {0}, soit {1}% des planètes", nbEarthSizePlanets, (nbEarthSizePlanets / nbPlanets * 100).ToString("#.##"));
                sW.WriteLine("Nombre de planètes du poids de la Terre à +/- 20% : {0}, soit {1}% des planètes", nbEarthWeightPlanets, (nbEarthWeightPlanets / nbPlanets * 100).ToString("#.##"));
                sW.WriteLine("Nombre de planètes du poids ET de la taille de la Terre à +/- 20% : {0}, soit {1}% des planètes", nbEarthSizeAndWeightPlanets, (nbEarthSizeAndWeightPlanets / nbPlanets * 100).ToString("#.##"));
                sW.WriteLine("Nombre de planètes à l'atmosphère comparable à la Terre (N = 78% +/- 1%, O2 = 21% +/- 1%) : {0}, soit {1}% des planètes", nbEarthAtmospherePlanets, (nbEarthAtmospherePlanets / nbPlanets * 100).ToString("#.##"));
                sW.WriteLine("Nombre de planètes semblables à la Terre (4 critères précédents réunis) : {0}, soit {1}% des planètes", nbFullDataEarthLike, (nbFullDataEarthLike / nbPlanets * 100).ToString("#.##"));
                sW.Close();
            }

            using (var sW = new StreamWriter(@"C:\galaxy.csv"))
            {
                sW.WriteLine("Nb planetes;Occurences");

                for (int i = 0; i < 20; i++)
                {
                    sW.WriteLine(i.ToString() + ";" + _galaxy.Where(x => x.Planets.Count == i).Count());
                }

                sW.Close();
            }
        }

        static void GenerateSystems(int systemsCount)
        {
            for (int i = 0; i < systemsCount; i++)
            {
                GenerateSystem();
            }
        }

        static void GenerateSystem()
        {
            //if (systemsCount < 100)
            //{
            Interlocked.Increment(ref _systemsCount);

            Console.WriteLine(_systemsCount);

            // la génération d'un système équivaut à créer une étoile et un nombre aléatoire de planètes
            var star = new Star();

            // classe de l'étoile
            var rnd = new Random();
            star.Class = Classes.StarClasses[_rndTable.Next(0, Classes.StarClasses.Count())];

            // déduction de l'ID de l'étoile à partir du compteur de systèmes
            star.ID = star.Class + "-" + _systemsCount.ToString();

            // génération des coordonées de l'étoile
            star.Coordinates[0] = _rndTable.Next(0, 100000);
            star.Coordinates[1] = _rndTable.Next(0, 100000);
            star.Coordinates[2] = _rndTable.Next(0, 100000);

            // génération des planètes
            var nbPlanets = _rndTable.Next(0, 20);
            for (int i = 0; i < nbPlanets; i++)
            {
                var planet = new Planet();

                // calcul de la position relative par rapport à l'étoile
                planet.DistanceFromStar = _rndTable.Next(0, 20);

                // calcul de la taille relative à la terre
                double diameter = _rndTable.Next(500, 50000);
                planet.RelativeToEarthSize = diameter / 12000.0;

                // calcul du poids relatif à la terre
                double coef = _rndTable.Next(500, 50000);
                planet.RelativeToEarthWeight = coef / 12000.0;

                // détermination de la composition de l'atmosphère
                int first = _rndTable.Next(0, 10000);
                int second = rnd.Next(0, 10000 - first);
                int third = rnd.Next(0, 10000 - first - second);
                int fourth = 10000 - first - second - third;

                double[] ratios = new double[] { first / 100d, second / 100d, third / 100d, fourth / 100d };
                planet.Atmosphere.SetRatios(_rndTable, ratios);

                // création de la planète terminée
                star.Planets.Add(planet);
            }

            _galaxy.Add(star);
            //}
        }

        static void w(string text, params object[] Params)
        {
            Console.WriteLine(text, Params);
        }

        static void SaveGame(string filename)
        {
            using (var sW = new StreamWriter(@"..\Bin\" + filename + ".sav", false))
            {
                sW.WriteLine("Joueur | " + _player.Name);
                sW.WriteLine("Compte | " + _player.Account);
                sW.WriteLine("Vaisseau | " + _player.ActualShip.Name);
                sW.WriteLine("Date | " + CurrentDateString());

                if (_player.ActualPlanet != null)
                    sW.WriteLine("Planète | {0}", _player.ActualPlanet.Name, _player.ActualPosition.Name);
                else
                    sW.WriteLine("Planète | ");

                sW.WriteLine("Système | {0}", _player.ActualPosition.Name);
                sW.WriteLine("Etat | {0}", _player.ActualState);

                sW.Close();
            }

            Console.Clear();
            w("Sauvegarde effectuée dans {0}.sav", filename);
            w(">");
            Console.ReadKey();
        }

        static string CurrentDateString()
        {
            return _time.Add(_timer.Elapsed).ToString();
        }

        static Tuple<Star, Planet> FindStarAndPlanetFromPlanetName(string planetName)
        {
            var planet = _galaxy.SelectMany(x => x.Planets).Where(x => x.Name.Equals(planetName)).First();
            var system = _galaxy.Where(x => x.Planets.Where(y => y.Name.Equals(planetName)).Count() == 1).First();
            return new Tuple<Star, Planet>(system, planet);
        }
    }
}
